<?php
/**
 * Shopp Extension Scaffold Copyright (c) 2012 Barry Hughes
 * 
 * 		This file is part of Shopp Extension Scaffold
 * 
 * 		Shopp Extension Scaffold is free software: you can redistribute it and/or modify
 * 		it under the terms of the GNU General Public License as published by
 * 		the Free Software Foundation, either version 3 of the License, or
 * 		(at your option) any later version.
 * 
 * 		Schopp Extension Scaffold is distributed in the hope that it will be useful,
 * 		but WITHOUT ANY WARRANTY; without even the implied warranty of
 * 		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * 		GNU General Public License for more details.
 * 
 * 		You should have received a copy of the GNU General Public License
 * 		along with Shopp Extension Scaffold. If not, see <http://www.gnu.org/licenses/>.
 */
 

/**
 * Forms a baseline and provides basic services to help integrate with Shopp.
 * Shopp must be present or the scaffold will (gracefully) collapse!
 *
 * To avoid the potential for multiple plugins all using different versions
 * of the scaffold then the "Extension" part of the classname should be
 * swapped out with something unique:
 *
 * 	default:   Shopp_Extension_Scaffold
 *	change to: Shopp_Search_Scaffold
 *
 * Might be suitable for a specialist search plugin, for example. Otherwise
 * multiple plugins could inadvertently declare the same class name.
 * 
 * @author Barry Hughes
 * @copyright 2012 Barry Hughes
 * @version 0.1.0 "Binoche"
 */
abstract class ShoppSE_Scaffold
{
	/**
	 * Used to identify the scaffolding.
	 */
	const CODENAME = 'Binoche';

	/**
	 * Dependency versions. Can be overwritten in the implementing
	 * class.
	 */
	protected $php_requirement   = '5.1';
	protected $shopp_requirement = '1.2';
	protected $wp_requirement    = '3.3';
	
	/**
	 * Flag indicates if Shopp has loaded successfully.
	 * 
	 * @var bool
	 */
	protected $shopp_ok = false;
	
	/**
	 * Container for details of any additional menu pages that might
	 * be required.
	 * 
	 * @var array
	 */
	protected $menu_pages = array();
	
	/**
	 * Container for the implementing object.
	 * 
	 * @var mixed
	 */
	public static $object;

	
	
	/**
	 * Sets up appropriate tests and defines any convenience variables 
	 * that might be required.
	 * 
	 * @return Shopp_Extension_Scaffold
	 */
	public function __construct() 
	{
		// Check basic environmental factors on activation
		register_activation_hook(__FILE__, array($this, 'preflight_checks'));

		// Create a static copy of the object reference
		self::$object = $this;
		
		// Hooks
		add_action('shopp_init', array($this, 'shopp_ready'));
		add_action('wp_loaded', array($this, 'ensure_shopp_started'));
		add_filter('shopp_admin_menus', array($this, 'add_menu_pages'));
	}
	
	
	/**
	 * Ascertains that minimum versions of PHP and WordPress are
	 * present and that a minimum version of the Shopp plugin is 
	 * active.
	 * 
	 * If any tests fail then the plugin will either not activate
	 * or, if the checks fail post-activation (for instance, Shopp
	 * is removed) then the plugin will be deactivated.
	 */
	protected function preflight_checks() 
	{
		if (version_compare($GLOBALS['wp_version'], $this->wp_requirement, '<'))
			exit('<p>WordPress 3.3 must be installed for this plugin to operate.</p>');
		
		if (version_compare(PHP_VERSION, $this->php_requirement, '<'))
			exit('<p>PHP 5.1 must be available for this plugin to operate.</p>');
	}


	/**
	 * This method will only fire if Shopp successfully loaded. The
	 * active version of Shopp can now be tested and the shopp_ok flag
	 * set.
	 */
	public function shopp_ready()
	{
		// Shopp minimum version check
		if (version_compare(SHOPP_VERSION, $this->shopp_requirement, '<'))
			return $this->deactivate();
			
		// Set the flag and get things rolling
		$this->shopp_ok = true;
		$this->begin();
	}
	
	
	/**
	 * If Shopp did not load then forcibly deactivate.
	 */
	public function ensure_shopp_started()
	{
		if (!$this->shopp_ok) $this->deactivate();
	}
	
	
	/**
	 * This method is the point-of-entry for the plugin proper.
	 */
	abstract public function begin();
	
	
	/**
	 * Forcibily deactivates the plugin. 
	 * 
	 * If this (the scaffolding class) is in a separate file from the 
	 * implementing class then that class *must* define $plugin_file 
	 * as a member variable, containing the name of the main plugin 
	 * file.
	 */
	public function deactivate()
	{
		// Plugin details
		$current = (array) get_option('active_plugins');
		$revised = array();
		
		// Main plugin filename
		$filename = isset($this->plugin_file) 
			? $this->plugin_file : '/'.basename(__FILE__);
		
		// Remove this plugin file from the array
		foreach ($current as $plugin) 
			if (strpos($plugin, $filename) === false)
				$revised[] = $plugin;
		
		// Update the active_plugins data
		update_option('active_plugins', $revised);
	}
	
	
	/**
	 * Adds a new page to the Shopp menu. This effectively aliases the
	 * add_menu_page() method.
	 * 
	 * @param string $id
	 * @param string $class
	 * @param string $name
	 * @param string $capability
	 * @param string $docs = false
	 */
	public function add_shopp_page($id, $class, $name, $capability = 'shopp_settings_system', $docs = false)
	{
		$this->add_menu_page($id, $class, $name, $capability, $docs);
	}
	
	
	/**
	 * Adds a new page to the catalog menu.
	 * 
	 * @param string $id
	 * @param string $class
	 * @param string $name
	 * @param string $capability
	 * @param string $docs = false
	 */
	public function add_catalog_page($id, $class, $name, $capability = 'shopp_settings_system', $docs = false)
	{
		$this->add_menu_page($id, $class, $name, $capability, $docs, 'products');
	}
	
	
	/**
	 * Adds a new page to the setup menu.
	 * 
	 * @param string $id
	 * @param string $class
	 * @param string $name
	 * @param string $capability
	 * @param string $docs = false
	 */
	public function add_setup_page($id, $class, $name, $capability = 'shopp_settings_system', $docs = false)
	{
		$this->add_menu_page($id, $class, $name, $capability, $docs, 'settings');
	}
	
	
	/**
	 * Adds a new menu page to be registered for deeper integration with
	 * the Shopp UI. For the sake of convenience and more readable code
	 * the following methods may be used instead:
	 * 
	 * 	add_catalog_page()
	 * 	add_setup_page()
	 * 	add_shopp_page()
	 * 
	 * @param string $id
	 * @param string $class
	 * @param string $name
	 * @param string $capability = 'shopp_settings_system' 
	 * @param string $docs = false
	 * @param string $parent = false
	 */
	public function add_menu_page($id, $class, $name, $capability = 'shopp_settings_system', $docs = false, $parent = false)
	{
		$this->menu_pages[] = array
		(
			'capability' => $capability,
			'class' => $class,
			'docs' => $docs,
			'id' => $id,
			'name' => $name,
			'parent' => $parent
		);
	}
	
	
	
	/**
	 * Adds any menu pages specified in the implementing class.
	 * 
	 * @param FlowController $admin
	 */
	public function add_menu_pages(FlowController $admin) 
	{
		// Check for additional menu page requests
		if (!empty($this->menu_pages))
		{ 
			// Attempt to register all additional $menu_pages
			foreach ($this->menu_pages as $page)
			{
				// The $page element itself must be an array
				if (!is_array($page)) continue;
				
				// Check for required indices
				foreach (array('capability', 'class', 'id', 'name') as $index)
					if (!array_key_exists($index, $page)) contine;
				
				// Set the required capability
				$admin->caps[$page['id']] = $page['capability'];
				
				$cap = $page['capability'];
				new ShoppError("Declared capability - $cap", false, SHOPP_DEBUG_ERR);
				
				// Optional parameters
				$docs = array_key_exists('docs', $page) ? $page['docs'] : false;
				$parent = array_key_exists('parent', $page) ? $page['parent'] : false;
				
				// Add our page
				$admin->addpage($page['id'], $page['name'], $page['class'], $docs, $parent);
			}
		}
	}
}

